﻿using Albatross.WPF.Common.PubSub;
using Albatross.WPF.Common.ViewModels;
using Albatross.WPF.Common.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace Albatross.WPF.Common{
	/// <summary>
	/// The IViewService interface gives the ViewModel the ability to interact with the associated View.  Different views can have different implementation
	/// of view services.  Use AttachedProperty.ViewServiceProperty to attach a view service instance with the View.  The property was created with
	/// the FrameworkPropertyMetadataOptions.Inherits flag.  Once the view service is declared on a view, its logical children will be able to access the
	/// same ViewService unless they declare their own.
	/// 
	/// There are two ways to access the ViewService.  1) When binding to a Command, pass the ViewService as a Command Parameter.
	/// 2) Use the ViewServiceDispatcher behavior to send the view service to a property on the view model.  The binding should be OneWayToSource.
	/// 
	/// </summary>
	public interface IViewService {
		/// <summary>
		/// Get a reference to the local publisher that's associated with the Albatross Shell Window
		/// </summary>
		IPublisher Publisher { get; }
		/// <summary>
		/// Associated the View Service instance with a UI component.  Each instance can only be assoicated once, therefore subsequent Init calls will
		/// have no effect.  This method is used by the AttachedProperties class.
		/// </summary>
		/// <param name="elem">The UI element to be associated</param>
		void LoadElement(UIElement elem);
		void UnloadElement(UIElement elem);

		bool? ShowDialog(IWorkspaceConstruct construct, int? width, int? height);
		void ShowWorkspace(IWorkspaceConstruct construct);
		bool CloseWorkspace(IWorkspaceViewModel vm);
		void CreateWindow(IWorkspaceConstruct construct);

		void ShowWarningMessageBox(string msg);
		void ShowErrorMessageBox(string msg);
		void ShowInfoMessageBox(string msg);
		bool ShowConfirmationMessageBox(string msg);
	}

	public static class ViewServiceExt {
		public static void ShowWorkspace(this IWorkspaceConstruct construct, IViewService viewService) {
			viewService.ShowWorkspace(construct);
		}
		public static void ShowDialog(this IWorkspaceConstruct construct, IViewService viewService, int? width, int? height) {
			viewService.ShowDialog(construct, width, height);
		}
		public static void CreateWindow(this IWorkspaceConstruct construct, IViewService viewService) {
			viewService.CreateWindow(construct);
		}

		public static void ShowWorkspace(this IViewService viewService, WorkspaceViewModel vm) {
			viewService.ShowWorkspace(WorkspaceConstructExt.CreateByObject(vm));
		}
		public static void ShowWorkspace<T>(this IViewService viewService) where T : IWorkspaceViewModel {
			viewService.ShowWorkspace(new WorkspaceConstruct<T>().SetWorkspaceID(0));
		}
		public static void ShowWorkspace<T>(this IViewService viewService, object workspaceID, params object[] args) where T : WorkspaceViewModel {
			viewService.ShowWorkspace(new WorkspaceConstruct<T>(args).SetWorkspaceID(workspaceID));
		}
		public static IWorkspaceConstruct Create(Type type, params object[] args) {
			if (type.IsSubclassOf(typeof(IWorkspaceViewModel))) {
				Type genericType = typeof(IWorkspaceConstruct<>).MakeGenericType(type);
				return (IWorkspaceConstruct)Activator.CreateInstance(genericType);
			} else {
				throw new ArgumentException();
			}
		}
		public static void ShowWorkspace(this IViewService viewService, Type type, object workspaceID, object[] args){
			viewService.ShowWorkspace(Create(type, args).SetWorkspaceID(workspaceID));
		}
		public static void ShowRegionalWorkspace(this IViewService viewService, Type type, string region, params object[] args) {
			viewService.ShowWorkspace(Create(type, args).SetRegion(region));
		}
		public static void ShowRegionalWorkspace<T>(this IViewService viewService, string region, params object[] args) where T : WorkspaceViewModel {
			viewService.ShowWorkspace(new WorkspaceConstruct<T>(args).SetRegion(region));
		}

		public static void CreateWindow(this IViewService viewService, WorkspaceViewModel vm) {
			viewService.CreateWindow(WorkspaceConstructExt.CreateByObject(vm));
		}
		public static void CreateWindow<T>(this IViewService viewService, object workspaceID, object[] args) where T : WorkspaceViewModel {
			viewService.CreateWindow(new WorkspaceConstruct<T>(args).SetWorkspaceID(workspaceID));
		}
		public static void CreateWindow(this IViewService viewService, Type type, object workspaceID, object[] args) {
			viewService.CreateWindow(Create(type, args).SetWorkspaceID(workspaceID));
		}
		public static void ShowDialog(this IViewService viewService, WorkspaceViewModel vm, int? width, int? height) {
			viewService.ShowDialog(WorkspaceConstructExt.CreateByObject(vm), width, height);
		}
		public static void ShowDialog<T>(this IViewService viewService, int? width, int? height, params object[] args) where T : WorkspaceViewModel {
			viewService.ShowDialog(new WorkspaceConstruct<T>(args), width, height);
		}
		public static void ShowDialog(this IViewService viewService, Type type, int? width, int? height, params object[] args) {
			viewService.ShowDialog(Create(type, args), width, height);
		}

		public static IViewService GetViewService(this FrameworkElement elem) {
			return (IViewService)elem.GetValue(Attached.ViewServiceProperty);
		}

		public static Stack<FrameworkElement> FindAllViews(FrameworkElement view) {
			Stack<FrameworkElement> stack = new Stack<FrameworkElement>();
			FindAllViews(view, stack, new HashSet<WorkspaceViewModel>());
			return stack;
		}
		static void FindAllViews(FrameworkElement view, Stack<FrameworkElement> tree, HashSet<WorkspaceViewModel> workspaces) {
			if (view.DataContext is WorkspaceViewModel) {
				if (!workspaces.Contains(view.DataContext)) {
					tree.Push(view);
				}
			}
			foreach (object child in LogicalTreeHelper.GetChildren(view)) {
				if (child is FrameworkElement) {
					FindAllViews((FrameworkElement)child, tree, workspaces);
				}
			}
		}

		public static bool CanCloseView(FrameworkElement view, HashSet<WorkspaceViewModel> workspaces, Stack<FrameworkElement> tree) {
			IViewService svcs = (IViewService)view.GetValue(Attached.ViewServiceProperty);
			if (view.DataContext is WorkspaceViewModel) {
				WorkspaceViewModel vm = (WorkspaceViewModel)view.DataContext;
				if (!workspaces.Contains(vm)) {
					if(!vm.Closing(svcs)){
						return false;
					} else {
						workspaces.Add(vm);
						tree.Push(view);
					}
				}
				foreach (object child in LogicalTreeHelper.GetChildren(view)) {
					if (child is FrameworkElement) {
						if(!CanCloseView((FrameworkElement)child, workspaces, tree)){
							return false;
						}
					}
				}
			}
			return true;
		}

		public static bool TryCloseView(params FrameworkElement[] views) {
			HashSet<WorkspaceViewModel> workspaces = new HashSet<WorkspaceViewModel>();
			Stack<FrameworkElement> tree = new Stack<FrameworkElement>();
			foreach (FrameworkElement view in views) {
				if (!CanCloseView(view, workspaces, tree)) {
					return false;
				}
			}
			while (tree.Count > 0) {
				FrameworkElement elem = tree.Pop();
				IWorkspaceConstruct construct = (IWorkspaceConstruct)elem.GetValue(Attached.WorkspaceConstructProperty);
				IDisposable dataContext = elem.DataContext as IDisposable;
				elem.DataContext = null;
				if (dataContext != null && (construct == null || !construct.KeepAlive)) {
					dataContext.Dispose();
				}
			}
			return true;
		}
		public static bool? ShowDialog<T>(Window owner, IWorkspaceConstruct construct, int? width, int? height) where T:Window, new() {
			T win = new T();
			UserControl view = construct.GetView();
			win.Content = view;
			win.Owner = owner;
			win.DataContext = construct.Build();
			if (width == null && height == null) {
				win.SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
			} else if (width == null) {
				win.SizeToContent = System.Windows.SizeToContent.Width;
			} else if (height == null) {
				win.SizeToContent = System.Windows.SizeToContent.Height;
			} else {
				win.SizeToContent = System.Windows.SizeToContent.Manual;
			}
			win.SetValue(Attached.WorkspaceConstructProperty, construct);
			if (construct.Constructed.AsyncLoadHandler != null) {
				construct.Constructed.AsyncLoadHandler((IViewService)win.GetValue(Attached.ViewServiceProperty));
			}
			return win.ShowDialog();
		}
	}
}
